﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Core.Modbus
{
    public class ModbusHelper
    {
        public const ushort MB_NO_ERROR = 0x0000;
        public const ushort MB_ADDR_ERROR = 0x0001;
        public const ushort MB_FUN_ERROR = 0x0002;
        public const ushort MB_CRC16_ERROR = 0x0004;
        public const ushort MB_FRAME_ERROR = 0x0008;
        public const ushort MB_LEN_ERROR = 0x0010;
        public const ushort COMPORT_NOT_OPEN = 0xFFFF;

        static readonly object ProSned = new object();
        static readonly object ProRec = new object();

        private ModbusHelper()
        {

        }

        public static ushort GetCRC16(byte[] DataIn)
        {
            ushort wCRC = 0xFFFF;
            for (int i = 0; i < DataIn.Length - 2; i++)
            {
                wCRC = (ushort)(wCRC ^ DataIn[i]);
                for (int j = 0; j < 8; j++)
                {
                    if ((wCRC & 0x01) == 1)
                    {
                        wCRC = (ushort)((wCRC >> 1) ^ 0xA001);
                    }
                    else
                    {
                        wCRC = (ushort)((wCRC) >> 1);
                    }
                }
            }
            return wCRC;
        }

        static bool CheckCRC(byte[] pbyInBuf) // Check CRC
        {
            ushort crc16;
            if (pbyInBuf.Length > 2)
            {
                crc16 = BitConverter.ToUInt16(pbyInBuf, pbyInBuf.Length - 2);
            }
            else
            {
                return false;
            }

            if (crc16 == GetCRC16(pbyInBuf))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 打包发送数据  需要先初始化ModbusInfo结构中除byte[] 数组外的信息，
        /// DataTag为附带信息 如功能码 0x05 0x06 0x10 需对其赋值 
        /// </summary>
        /// <param name="SndMb"></param>
        /// <returns></returns>
        public static bool MakeSendFrame(ref ModbusInfo SndMb)
        {
            byte[] byToSend;
            int length;

            if (!Monitor.TryEnter(ProSned, 100))
            {
                return false;
            }
            if (SndMb == null)
            {
                throw new NullReferenceException();
                //return false;
            }
            try
            {
                switch (SndMb.FunCode)
                {
                    case ModbusCode.F01:
                    case ModbusCode.F02:
                        byToSend = new byte[8];
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        length = 6;
                        break;
                    case ModbusCode.F03:
                    case ModbusCode.F04:
                        byToSend = new byte[8];
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        length = 6;
                        break;
                    case ModbusCode.F05:
                        byToSend = new byte[8];
                        if (SndMb.WriteData == null || SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        if (SndMb.WriteData[1] != 0x00 || (SndMb.WriteData[0] != 0x00 && SndMb.WriteData[0] != 0xFF))
                        {
                            return false;
                        }
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = SndMb.WriteData[0];
                        byToSend[5] = SndMb.WriteData[1];
                        length = 6;
                        break;
                    case ModbusCode.F06:
                        byToSend = new byte[8];
                        if (SndMb.WriteData == null || SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = SndMb.WriteData[0];
                        byToSend[5] = SndMb.WriteData[1];
                        length = 6;
                        break;
                    case ModbusCode.F10:
                        if (SndMb.WriteData == null || SndMb.WriteData.Length != SndMb.RegNum * 2)
                        {
                            return false;
                        }
                        byToSend = new byte[SndMb.WriteData.Length + 9];

                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        byToSend[6] = (byte)(SndMb.RegNum * 2);
                        for (int i = 0; i < SndMb.WriteData.Length; i++)
                        {
                            byToSend[7 + i] = SndMb.WriteData[i];
                        }
                        length = (ushort)(SndMb.WriteData.Length + 7);
                        break;
                    default:
                        byToSend = new byte[2];
                        length = 0;
                        break;

                }
                byToSend[0] = SndMb.DevAddr;
                byToSend[1] = (byte) SndMb.FunCode;
                if (length == 0)
                {
                    return false;
                }
                else
                {
                    ushort crc = GetCRC16(byToSend);
                    byToSend[length] = (byte)(crc & 0x00FF);
                    byToSend[length + 1] = (byte)(crc >> 8);
                    SndMb.SendADU = byToSend;
                    //SndMb.RecADU = null;
                }
                return true;
            }
            catch
            {
                return false;
            }
            finally
            {
                Monitor.Exit(ProSned);
            }
        }

        /// <summary>
        /// 检查接收数据正确性(需先将接收报文传给RecADU)
        /// </summary>
        /// <param name="ModData"></param>
        /// <returns></returns>
        public static ushort CheckRecFrame(ref ModbusInfo ModData)  // check receive data frame
        {
            if (Monitor.TryEnter(ProRec, 200) == false) // 等待失败
            {
                return MB_FRAME_ERROR;
            }

            if (ModData == null || ModData.SendADU == null || ModData.RecADU == null)
            {
                Monitor.Exit(ProRec);
                //throw new NullReferenceException();
                return MB_FRAME_ERROR;
            }
            byte[] pBuffer = ModData.RecADU;
            ushort length = (ushort)ModData.RecADU.Length;
            try
            {
                if (pBuffer.Length < 5 || length < 5)
                {
                    return MB_LEN_ERROR;
                }
                else if (ModData.DevAddr != pBuffer[0])
                {
                    return MB_ADDR_ERROR;
                }
                else if ((byte)ModData.FunCode != pBuffer[1] && (byte)ModData.FunCode != pBuffer[1] - 0x80)
                {
                    return MB_FUN_ERROR;
                }
                else if (!CheckCRC(pBuffer))
                {
                    return MB_CRC16_ERROR;
                }
                else
                {
                    if ((byte)ModData.FunCode < 0x80)
                    {
                        switch (ModData.FunCode)
                        {
                            case ModbusCode.F10:
                                if (length != 8)
                                {
                                    return MB_LEN_ERROR;
                                }
                                break;
                            case ModbusCode.F02:
                            case ModbusCode.F01:
                                int regnum = ModData.RegNum / 8;
                                if (ModData.RegNum % 8 != 0)
                                {
                                    if (pBuffer[2] != regnum + 1 || length != regnum + 1 + 5)
                                    {
                                        return MB_LEN_ERROR;
                                    }
                                }
                                else
                                {
                                    if (pBuffer[2] != regnum || length != regnum + 5)
                                    {
                                        return MB_LEN_ERROR;
                                    }
                                }
                                break;
                            case ModbusCode.F03:
                            case ModbusCode.F04:
                                if (length != pBuffer[2] + 5)
                                {
                                    return MB_LEN_ERROR;
                                }
                                break;
                            case ModbusCode.F05:
                            case ModbusCode.F06:
                                if (length != 8) // 固定长度
                                {
                                    return MB_LEN_ERROR;
                                }
                                else if (pBuffer[4] != ModData.SendADU[4] || pBuffer[5] != ModData.SendADU[5]) // 回的内容 和发的内容一致
                                {
                                    return MB_FRAME_ERROR;
                                }
                                break;
                            default:
                            {
                                return MB_FUN_ERROR;
                            }
                        }
                        return MB_NO_ERROR;
                    }
                    else
                    {
                        return MB_FRAME_ERROR;
                    }
                }
            }
            finally
            {
                Monitor.Exit(ProRec);
            }
        }

        #region 改进后的Modbus协议
        public static bool MakeSendFrame(ref ModbusRxTxCommand SndMb)
        {
            byte[] byToSend;
            int length;

            if (!Monitor.TryEnter(ProSned, 100))
            {
                return false;
            }
            if (SndMb == null)
            {
                throw new NullReferenceException();
                //return false;
            }
            try
            {
                switch (SndMb.FunCode)
                {
                    case ModbusCode.F01:
                    case ModbusCode.F02:
                        byToSend = new byte[8];
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        length = 6;
                        break;
                    case ModbusCode.F03:
                    case ModbusCode.F04:
                        byToSend = new byte[8];
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        length = 6;
                        break;
                    case ModbusCode.F05:
                        byToSend = new byte[8];
                        if (SndMb.WriteData == null || SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        if (SndMb.WriteData[1] != 0x00 || (SndMb.WriteData[0] != 0x00 && SndMb.WriteData[0] != 0xFF))
                        {
                            return false;
                        }
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = SndMb.WriteData[0];
                        byToSend[5] = SndMb.WriteData[1];
                        length = 6;
                        break;
                    case ModbusCode.F06:
                        byToSend = new byte[8];
                        if (SndMb.WriteData == null || SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = SndMb.WriteData[0];
                        byToSend[5] = SndMb.WriteData[1];
                        length = 6;
                        break;
                    case ModbusCode.F10:
                        if (SndMb.WriteData == null || SndMb.WriteData.Length != SndMb.RegNum * 2)
                        {
                            return false;
                        }
                        byToSend = new byte[SndMb.WriteData.Length + 9];

                        byToSend[2] = (byte)(SndMb.RegAddr >> 8);
                        byToSend[3] = (byte)(SndMb.RegAddr & 0x00FF);
                        byToSend[4] = (byte)(SndMb.RegNum >> 8);
                        byToSend[5] = (byte)(SndMb.RegNum & 0x00FF);
                        byToSend[6] = (byte)(SndMb.RegNum * 2);
                        for (int i = 0; i < SndMb.WriteData.Length; i++)
                        {
                            byToSend[7 + i] = SndMb.WriteData[i];
                        }
                        length = (ushort)(SndMb.WriteData.Length + 7);
                        break;
                    default:
                        byToSend = new byte[2];
                        length = 0;
                        break;

                }
                byToSend[0] = SndMb.DevAddr;
                byToSend[1] = (byte)SndMb.FunCode;
                if (length == 0)
                {
                    return false;
                }
                else
                {
                    ushort crc = GetCRC16(byToSend);
                    byToSend[length] = (byte)(crc & 0x00FF);
                    byToSend[length + 1] = (byte)(crc >> 8);
                    SndMb.SendBytes = byToSend;
                    //SndMb.RecADU = null;
                }
                return true;
            }
            catch
            {
                return false;
            }
            finally
            {
                Monitor.Exit(ProSned);
            }
        }
        /// <summary>
        /// 检查接收数据正确性(需先将接收报文传给RecADU)
        /// </summary>
        /// <param name="ModData"></param>
        /// <returns></returns>
        public static ushort CheckRecFrame(ModbusRxTxCommand ModData)  // check receive data frame
        {
            if (Monitor.TryEnter(ProRec, 200) == false) // 等待失败
            {
                return MB_FRAME_ERROR;
            }

            if (ModData == null || ModData.SendBytes == null || ModData.ReceiveBytes == null)
            {
                Monitor.Exit(ProRec);
                //throw new NullReferenceException();
                return MB_FRAME_ERROR;
            }
            byte[] pBuffer = ModData.ReceiveBytes;
            ushort length = (ushort)ModData.ReceiveBytes.Length;
            try
            {
                if (pBuffer.Length < 5 || length < 5)
                {
                    return MB_LEN_ERROR;
                }
                else if (ModData.DevAddr != pBuffer[0])
                {
                    return MB_ADDR_ERROR;
                }
                else if ((byte)ModData.FunCode != pBuffer[1] && (byte)ModData.FunCode != pBuffer[1] - 0x80)
                {
                    return MB_FUN_ERROR;
                }
                else if (!CheckCRC(pBuffer))
                {
                    return MB_CRC16_ERROR;
                }
                else
                {
                    if ((byte)ModData.FunCode < 0x80)
                    {
                        switch (ModData.FunCode)
                        {
                            case ModbusCode.F10:
                                if (length != 8)
                                {
                                    return MB_LEN_ERROR;
                                }
                                break;
                            case ModbusCode.F02:
                            case ModbusCode.F01:
                                int regnum = ModData.RegNum / 8;
                                if (ModData.RegNum % 8 != 0)
                                {
                                    if (pBuffer[2] != regnum + 1 || length != regnum + 1 + 5)
                                    {
                                        return MB_LEN_ERROR;
                                    }
                                }
                                else
                                {
                                    if (pBuffer[2] != regnum || length != regnum + 5)
                                    {
                                        return MB_LEN_ERROR;
                                    }
                                }
                                break;
                            case ModbusCode.F03:
                            case ModbusCode.F04:
                                if (length != pBuffer[2] + 5)
                                {
                                    return MB_LEN_ERROR;
                                }
                                break;
                            case ModbusCode.F05:
                            case ModbusCode.F06:
                                if (length != 8) // 固定长度
                                {
                                    return MB_LEN_ERROR;
                                }
                                else if (pBuffer[4] != ModData.SendBytes[4] || pBuffer[5] != ModData.SendBytes[5]) // 回的内容 和发的内容一致
                                {
                                    return MB_FRAME_ERROR;
                                }
                                break;
                            default:
                            {
                                return MB_FUN_ERROR;
                            }
                        }
                        return MB_NO_ERROR;
                    }
                    else
                    {
                        return MB_FRAME_ERROR;
                    }
                }
            }
            finally
            {
                Monitor.Exit(ProRec);
            }
        }
        #endregion

        #region Tcp Modbus

        public static bool MakeTcpSendFrame(ushort index, ref ModbusRxTxCommand SndMb)
        {
            List<byte> byToSend = new List<byte>();

            if (!Monitor.TryEnter(ProSned, 100))
            {
                return false;
            }
            if (SndMb == null)
            {
                throw new NullReferenceException();
                //return false;
            }
            try
            {
                byToSend.Add(SndMb.DevAddr);
                byToSend.Add((byte)SndMb.FunCode);
                switch (SndMb.FunCode)
                {
                    case ModbusCode.F01:
                    case ModbusCode.F02:
                        byToSend.Add((byte)(SndMb.RegAddr >> 8));
                        byToSend.Add((byte)(SndMb.RegAddr & 0x00FF));
                        byToSend.Add((byte)(SndMb.RegNum >> 8));
                        byToSend.Add((byte)(SndMb.RegNum & 0x00FF));
                        break;
                    case ModbusCode.F03:
                    case ModbusCode.F04:
                        byToSend.Add((byte)(SndMb.RegAddr >> 8));
                        byToSend.Add((byte)(SndMb.RegAddr & 0x00FF));
                        byToSend.Add((byte)(SndMb.RegNum >> 8));
                        byToSend.Add((byte)(SndMb.RegNum & 0x00FF));
                        break;
                    case ModbusCode.F05:
                        if (SndMb.WriteData == null || SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        if (SndMb.WriteData[1] != 0x00 || (SndMb.WriteData[0] != 0x00 && SndMb.WriteData[0] != 0xFF))
                        {
                            return false;
                        }
                        byToSend.Add((byte)(SndMb.RegAddr >> 8));
                        byToSend.Add((byte)(SndMb.RegAddr & 0x00FF));
                        byToSend.Add(SndMb.WriteData[0]);
                        byToSend.Add(SndMb.WriteData[1]);
                        break;
                    case ModbusCode.F06:
                        if (SndMb.WriteData == null || SndMb.WriteData.Length < 2)
                        {
                            return false;
                        }
                        byToSend.Add((byte)(SndMb.RegAddr >> 8));
                        byToSend.Add((byte)(SndMb.RegAddr & 0x00FF));
                        byToSend.Add(SndMb.WriteData[0]);
                        byToSend.Add(SndMb.WriteData[1]);
                        break;
                    case ModbusCode.F10:
                        if (SndMb.WriteData == null || SndMb.WriteData.Length != SndMb.RegNum * 2)
                        {
                            return false;
                        }

                        byToSend.Add((byte)(SndMb.RegAddr >> 8));
                        byToSend.Add((byte)(SndMb.RegAddr & 0x00FF));
                        byToSend.Add((byte)(SndMb.RegNum >> 8));
                        byToSend.Add((byte)(SndMb.RegNum & 0x00FF));
                        byToSend.Add((byte)(SndMb.RegNum * 2));
                        for (int i = 0; i < SndMb.WriteData.Length; i++)
                        {
                            byToSend.Add(SndMb.WriteData[i]);
                        }

                        break;

                }

                if (byToSend.Count == 0)
                {
                    return false;
                }
                else
                {
                    List<byte> prefix = new List<byte>();
                    prefix.AddRange(BitConverter.GetBytes(index).Reverse());
                    prefix.Add(0);
                    prefix.Add(0);
                    prefix.AddRange(BitConverter.GetBytes((ushort)byToSend.Count).Reverse());
                    prefix.AddRange(byToSend);
                    SndMb.SendBytes = prefix.ToArray();
                    //SndMb.RecADU = null;
                }
                return true;
            }
            catch
            {
                return false;
            }
            finally
            {
                Monitor.Exit(ProSned);
            }
        }
        /// <summary>
        /// 检查接收数据正确性(需先将接收报文传给RecADU)
        /// </summary>
        /// <param name="ModData"></param>
        /// <returns></returns>
        public static ushort CheckTcpRecFrame(ModbusRxTxCommand ModData)  // check receive data frame
        {
            if (Monitor.TryEnter(ProRec, 200) == false) // 等待失败
            {
                return MB_FRAME_ERROR;
            }

            if (ModData == null || ModData.SendBytes == null || ModData.ReceiveBytes == null)
            {
                Monitor.Exit(ProRec);
                //throw new NullReferenceException();
                return MB_FRAME_ERROR;
            }
            if (ModData.ReceiveBytes.Length < 6)
            {
                return MB_LEN_ERROR;
            }
            byte[] receiveBytes = ModData.ReceiveBytes;
            byte[] pBuffer = new byte[receiveBytes.Length - 6];
            Buffer.BlockCopy(receiveBytes, 6, pBuffer, 0, receiveBytes.Length - 6);
            byte[] lenbytes = new byte[2];
            Buffer.BlockCopy(receiveBytes, 4, lenbytes, 0, 2);
            ushort length = BitConverter.ToUInt16(lenbytes.Reverse().ToArray(), 0);
            try
            {
                if (length != receiveBytes.Length - 6)
                {
                    return MB_LEN_ERROR;
                }
                else if (ModData.DevAddr != pBuffer[0])
                {
                    return MB_ADDR_ERROR;
                }
                else if ((byte)ModData.FunCode != pBuffer[1] && (byte)ModData.FunCode != pBuffer[1] - 0x80)
                {
                    return MB_FUN_ERROR;
                }
                else
                {
                    if ((byte)ModData.FunCode < 0x80)
                    {
                        switch (ModData.FunCode)
                        {
                            case ModbusCode.F10:
                                if (length != 6)
                                {
                                    return MB_LEN_ERROR;
                                }
                                break;
                            case ModbusCode.F02:
                            case ModbusCode.F01:
                                int regnum = ModData.RegNum / 8;
                                if (ModData.RegNum % 8 != 0)
                                {
                                    if (pBuffer[2] != regnum + 1 || length != regnum + 1 + 3)
                                    {
                                        return MB_LEN_ERROR;
                                    }
                                }
                                else
                                {
                                    if (pBuffer[2] != regnum || length != regnum + 3)
                                    {
                                        return MB_LEN_ERROR;
                                    }
                                }
                                break;
                            case ModbusCode.F03:
                            case ModbusCode.F04:
                                if (length != pBuffer[2] + 3)
                                {
                                    return MB_LEN_ERROR;
                                }
                                break;
                            case ModbusCode.F05:
                            case ModbusCode.F06:
                                if (length != 6) // 固定长度
                                {
                                    return MB_LEN_ERROR;
                                }
                                else if (pBuffer[4] != ModData.SendBytes[10] || pBuffer[5] != ModData.SendBytes[11]) // 回的内容 和发的内容一致
                                {
                                    return MB_FRAME_ERROR;
                                }
                                break;
                            default:
                            {
                                return MB_FUN_ERROR;
                            }
                        }
                        return MB_NO_ERROR;
                    }
                    else
                    {
                        return MB_FRAME_ERROR;
                    }
                }
            }
            finally
            {
                Monitor.Exit(ProRec);
            }
        }

        #endregion
    }
}
